package socket

import (
	"fmt"
	"log/slog"
	"net"
	"time"
)

type Client interface {
	Dial(address string) (*Socket, error)
	DialTimeout(address string, timeout time.Duration) (*Socket, error)
	Conn() net.Conn
	Close() error
}

type TcpClient struct {
	tcpConn *net.TCPConn
}

func NewTcpClient() *TcpClient {
	return &TcpClient{}
}

func (t *TcpClient) Dial(address string) (*Socket, error) {
	return t.DialTimeout(address, time.Second*30)
}

func (t *TcpClient) DialTimeout(address string, timeout time.Duration) (*Socket, error) {
	tcpAddr, err := net.ResolveTCPAddr("tcp", address)
	if err != nil {
		return nil, err
	}
	if conn, err := net.DialTimeout(tcpAddr.Network(), tcpAddr.String(), timeout); err != nil {
		return nil, err
	} else {
		slog.Debug(fmt.Sprintf("TCP client connection established successfully %s->%s", conn.LocalAddr(), conn.RemoteAddr()))
		t.tcpConn = conn.(*net.TCPConn)
		return NewSocket(t.tcpConn), nil
	}
}

func (t *TcpClient) Conn() net.Conn {
	return t.tcpConn
}

func (t *TcpClient) Close() error {
	if t.tcpConn != nil {
		return t.tcpConn.Close()
	}
	return nil
}

type UdpClient struct {
	udpConn *net.UDPConn
}

func NewUdpClient() *UdpClient {
	return &UdpClient{}
}

func (u *UdpClient) Dial(address string) (*Socket, error) {
	return u.DialTimeout(address, time.Second*30)
}

func (u *UdpClient) DialTimeout(address string, timeout time.Duration) (*Socket, error) {
	udpAddr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		return nil, err
	}

	if conn, err := net.DialTimeout(udpAddr.Network(), udpAddr.String(), timeout); err != nil {
		return nil, err
	} else {
		slog.Debug(fmt.Sprintf("UDP client connection established successfully %s->%s", conn.LocalAddr(), conn.RemoteAddr()))
		u.udpConn = conn.(*net.UDPConn)
		return NewSocket(u.udpConn), nil
	}
}

func (u *UdpClient) Conn() net.Conn {
	return u.udpConn
}

func (u *UdpClient) Close() error {
	return u.udpConn.Close()
}
